# -*- encoding:utf-8 -*-
# __author__ = 'chenyongbing'
# __poject__ = 'ops_cicd'
# __datetime__ = '2019/7/3'
# __name__ = 'kubernetes_helper'

from __future__ import unicode_literals

from kubernetes import client, config
import hashlib
import os
import base64
import json
import yaml
import re
import datetime
from dateutil import tz
import logging
logger = logging.getLogger('kubernetes')
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

class KubeClient():
    def __init__(self,name,yaml):
        self.conn = self.get_kubernetes_client(name=name,yaml=yaml)

    def k8s_api(self,kind=None):
        if kind == 'Deployment':
            return client.AppsV1Api()
        else:
            return client.CoreV1Api()

    def dump_kubernetes_config(self,name,yaml):
        dir = os.path.join(BASE_DIR , '.kube')
        if not os.path.exists(dir):
            os.mkdir(dir)
        config_file = os.path.join(dir,'kubeconfig_%s'%name)
        # if not os.path.exists(config_file) or hashlib.md5(open(config_file).read()).hexdigest()!=hashlib.md5(yaml).hexdigest():
        if not os.path.exists(config_file) :
            with open(config_file,'w') as f:
                f.write(yaml)
        return config_file


    def get_kubernetes_client(self,name,yaml):
        kube_config=self.dump_kubernetes_config(name,yaml)
        config.load_kube_config(config_file=kube_config)
        kube=client.CoreV1Api()
        # kube=client.ApiClient()
        return kube

    def get_namespaces(self):
        content = self.conn.list_namespace()
        return [item.metadata.name for item in content.items]

    def create_namespace(self,namespace):
        if namespace in self.get_namespaces():
            logger.info('namespace: %s already exists.'%namespace)
            return True
        else:
            body=client.V1Namespace(metadata=client.V1ObjectMeta(name=namespace))
            self.conn.create_namespace(body)
            logger.info('namespace: %s created.'%namespace)
            return True

    def read_namespaced_kind_info(self, kind, name=None, namespace=None):
        content = getattr(self.k8s_api(kind), 'read_namespaced_{0}'.format(kind.lower()))(name=name, namespace=namespace)
        return content


    def read_namespaced_deployment_info(self, name=None, namespace=None):
        content = self.read_namespaced_kind_info(kind='Deployment', name=name, namespace=namespace)
        return content.to_dict()

    def get_namespaced_kind_info(self,kind,namespace=None):
        content=getattr(self.k8s_api(kind),'list_namespaced_{0}'.format(kind.lower()))(namespace=namespace)
        return content

    def get_namespaced_kind_list(self,kind,namespace=None):
        content=getattr(self.k8s_api(kind),'list_namespaced_{0}'.format(kind.lower()))(namespace=namespace)
        return [item.metadata.name for item in content.items]

    def get_namespaced_pod_list(self,namespace=None,name=''):
        kind = 'Pod'
        content=getattr(self.k8s_api(kind), 'list_namespaced_{0}'.format(kind.lower()))(namespace=namespace)
        datas =  [{'name':item.metadata.name,
                 'ready':1 if item.status.container_statuses[0].ready else 0,
                 'status':item.status.phase,
                 'image_tag': item.status.container_statuses[0].image.split(':')[-1],
                 'pod_ip':item.status.pod_ip,
                 'start_time':item.status.start_time.astimezone(tz.gettz('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S'),
                 'start_age': str(datetime.datetime.now(tz.gettz('Asia/Shanghai')) - item.status.start_time.astimezone(tz.gettz('Asia/Shanghai'))).split('.')[0],
                 # 'start_age': str(datetime.datetime.now(tz=None) - item.status.start_time.replace(tzinfo=None)).split('.')[0],
                 'restart_count':item.status.container_statuses[0].restart_count,
                   'redeploy_timestamp': item.metadata.annotations.get('redeploy-timestamp')
                 } for item in content.items if re.search('{0}-[^-]+-[^-]+$'.format(name),item.metadata.name) ]
        logger.debug(datas)
        return datas




    def create_namespace_registry_secret(self,namespace=None,name=None,server=None,username=None,password=None):
        if name in self.get_namespaced_kind_list(kind='Secret',namespace=namespace):
            logger.info('namespace: %s secret key: %s already exists.'%(namespace,name))
            return True
        else:
            type = 'kubernetes.io/dockerconfigjson'
            data = {
                '.dockerconfigjson':base64.encodestring(json.dumps(
                    {
                        "auths":{
                            server:{
                                'username':username,
                                'password':password,
                                'auth':base64.encodestring('{username}:{password}'.format(username=username,password=password))
                            }
                        }
                    }
                ))
            }

            self.conn.create_namespaced_secret(namespace=namespace,body=client.V1Secret(
                                           metadata=client.V1ObjectMeta(name=name),
                                           type=type,
                                           data=data))
            return True


    def create_update_from_yaml(self,name=None,namespace=None,yaml_text=''):
        for yaml_object in yaml.safe_load_all(yaml_text):
            kind = yaml_object['kind']
            if name in self.get_namespaced_kind_list(kind=kind,namespace=namespace):
                self.create_update_from_yaml_single_item(client.ApiClient(), yaml_object, namespace=namespace,
                                                         action='patch',name=name)
            else:
                self.create_update_from_yaml_single_item(client.ApiClient(), yaml_object, namespace=namespace,
                                                         action='create')


    def delete_from_yaml(self,name=None,namespace=None,yaml_text=''):
        for yaml_object in yaml.safe_load_all(yaml_text):
            kind=yaml_object['kind']
            if name in self.get_namespaced_kind_list(kind=kind, namespace=namespace):
                self.delete_from_yaml_single_item(client.ApiClient(), yaml_object, namespace=namespace,
                                                      name=name)
            logger.info('kind: {0} namespace: {1} name: {2} deleted.'.format(kind, namespace, name))
        else:
            logger.info('kind: {0} namespace: {1} name: {2} not exists.'.format(kind,namespace,name))



    def delete_from_yaml_single_item(self,
                                     k8s_client,
                                     yml_object,
                                     verbose=False,
                                     namespace="default",
                                     **kwargs
                                     ):
        group, _, version=yml_object["apiVersion"].partition("/")
        if version == "":
            version=group
            group="core"
        # Take care for the case e.g. api_type is "apiextensions.k8s.io"
        # Only replace the last instance
        group="".join(group.rsplit(".k8s.io", 1))
        # convert group name from DNS subdomain format to
        # python class name convention
        group="".join(word.capitalize() for word in group.split('.'))
        fcn_to_call="{0}{1}Api".format(group, version.capitalize())
        k8s_api=getattr(client, fcn_to_call)(k8s_client)
        # Replace CamelCased action_type into snake_case
        kind=yml_object["kind"]
        kind=re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind)
        kind=re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower()
        # Decide which namespace we are going to put the object in,
        # if any
        if "namespace" in yml_object["metadata"]:
            namespace=yml_object["metadata"]["namespace"]
        # Expect the user to create namespaced objects more often
        if hasattr(k8s_api, "delete_namespaced_{0}".format(kind)):
            resp=getattr(k8s_api, "delete_namespaced_{0}".format( kind))(
                namespace=namespace, propagation_policy='Background', **kwargs)
        else:
            resp=getattr(k8s_api, "delete_{0}".format( kind))(
                **kwargs)
        if verbose:
            print("{0} created. status='{1}'".format(kind, str(resp.status)))

    def create_update_from_yaml_single_item(self,
            k8s_client,
            yml_object,
            verbose=False,
            namespace="default",
            action='create',
            **kwargs):
        group, _, version=yml_object["apiVersion"].partition("/")
        if version == "":
            version=group
            group="core"
        # Take care for the case e.g. api_type is "apiextensions.k8s.io"
        # Only replace the last instance
        group="".join(group.rsplit(".k8s.io", 1))
        # convert group name from DNS subdomain format to
        # python class name convention
        group="".join(word.capitalize() for word in group.split('.'))
        fcn_to_call="{0}{1}Api".format(group, version.capitalize())
        k8s_api=getattr(client, fcn_to_call)(k8s_client)
        # Replace CamelCased action_type into snake_case
        kind=yml_object["kind"]
        kind=re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind)
        kind=re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower()
        # Decide which namespace we are going to put the object in,
        # if any
        if "namespace" in yml_object["metadata"]:
            namespace=yml_object["metadata"]["namespace"]
        # Expect the user to create namespaced objects more often
        if hasattr(k8s_api, "{0}_namespaced_{1}".format(action,kind)):
            resp=getattr(k8s_api, "{0}_namespaced_{1}".format(action,kind))(
                body=yml_object, namespace=namespace, **kwargs)
        else:
            resp=getattr(k8s_api, "{0}_{1}".format(action,kind))(
                body=yml_object, **kwargs)
        if verbose:
            print("{0} created. status='{1}'".format(kind, str(resp.status)))


    def get_deployment_image_tag(self,name, containers):
        for container in containers:
            if re.search(container.name, name):
                image = container.image
                tag = image.split(':')[-1]
                return tag
        return None
    def get_deployment_status(self, name=None, namespace=None):
        kind = 'Deployment'
        content = getattr(self.k8s_api(kind), 'read_namespaced_{0}'.format(kind.lower()))(name=name,namespace=namespace)
        redeploy_timestamp = content.spec.template.metadata.annotations.get('redeploy-timestamp') if content.spec.template.metadata.annotations else ''
        # images = [container.image for container in content.spec.template.spec.containers]
        tag = self.get_deployment_image_tag(name, content.spec.template.spec.containers)

        data = {
            'status': {
                'available_replicas': content.status.available_replicas,
                'ready_replicas': content.status.ready_replicas,
                'replicas': content.status.replicas,
                'unavailable_replicas': content.status.unavailable_replicas,
                'observed_generation': content.status.observed_generation
            },
            'redeploy_timestamp': redeploy_timestamp,
            'imagetag': tag,
            'generation': content.metadata.generation,
            'replicas': content.spec.replicas
        }
        return data
    def check_deployment_rollout_status(self, name=None, namespace=None):
        kind = 'Deployment'
        content = getattr(self.k8s_api(kind), 'read_namespaced_{0}'.format(kind.lower()))(name=name,namespace=namespace)
        redeploy_timestamp = content.spec.template.metadata.annotations.get('redeploy-timestamp') if content.spec.template.metadata.annotations else ''
        if content.metadata.generation == content.status.observed_generation and content.status.ready_replicas==content.status.replicas and content.status.unavailable_replicas == None:
            return True
        return False
    def deployment_restart(self,name=None,namespace=None, redeploy_timestamp = None):
        """重启deploy"""
        redeploy_timestamp = redeploy_timestamp or str(int(datetime.datetime.now().timestamp()*1000))

        # body = client.V1Deployment(spec=client.AppsV1beta1DeploymentSpec(template=client.V1PodTemplateSpec(
        # metadata=client.V1ObjectMeta(labels={"updated": datetime.datetime.now().strftime('%Y%m%d%H%M%S')}))))
        body = client.V1Deployment(
            spec=client.V1DeploymentSpec(
                template=client.V1PodTemplateSpec(
                    metadata=client.V1ObjectMeta(
                        annotations={"redeploy-timestamp": redeploy_timestamp})
                ), selector=client.V1LabelSelector(match_labels={})))

        k8s_api=self.k8s_api(kind='Deployment')
        ret = k8s_api.patch_namespaced_deployment(name=name, namespace=namespace, body=body)
        return ret

    def deployment_scale(self,name=None,namespace=None,replicas=1):
        """扩缩容"""
        # body = client.V1Scale(metadata=client.V1ObjectMeta(name=name,namespace=namespace),spec=client.AppsV1beta1ScaleSpec(replicas=replicas))
        body = client.V1Scale(metadata=client.V1ObjectMeta(name=name,namespace=namespace),spec=client.V1ScaleSpec(replicas=replicas))
        k8s_api = self.k8s_api(kind='Deployment')
        k8s_api.patch_namespaced_deployment_scale(name=name,namespace=namespace,body=body)
        # k8s_api.patch_namespaced_deployment_scale_with_http_info()

    def deployment_update_image(self, name=None, namespace=None, image=None):
        """更新镜像"""
        # body = client.V1Deployment(spec=client.V1DeploymentSpec(template=client.V1PodTemplateSpec(spec=client.V1PodSpec(image=))))
        container_name = re.sub('-deployment', '', name)
        body = {"spec": {"template": {"spec": {"containers": [{"name": container_name, "image": image}]}}}}
        k8s_api = self.k8s_api(kind='Deployment')
        k8s_api.patch_namespaced_deployment(name=name, namespace=namespace, body=body)


    def deployment_update_resources_and_env(self, name=None, namespace=None, resources={}, envs=[]):
        '''更新资源'''
        container_name = re.sub('-deployment', '', name)
        body = {"spec": {"template": {"spec": {"containers": [{"name": container_name, "resources": resources, "env": envs}]}}}}
        k8s_api = self.k8s_api(kind='Deployment')
        k8s_api.patch_namespaced_deployment(name=name, namespace=namespace, body=body)

    def get_deployment_replicas(self, name=None, namespace=None):
        k8s_api = self.k8s_api(kind='Deployment')
        ret = k8s_api.read_namespaced_deployment(name=name, namespace=namespace)
        return ret.spec.replicas

    def get_deployment_resources(self, name=None, namespace=None):
        try:
            k8s_api = self.k8s_api(kind='Deployment')
            container_name = re.sub('-deployment', '', name)
            body = k8s_api.read_namespaced_deployment(name=name, namespace=namespace)
            for container in body.spec.template.spec.containers:
                if name == container.name or re.search(container.name, name) or container_name == container.name:
                    resources = container.resources
                    resources_json = {'limits': resources.limits, 'requests': resources.requests}
                    # resources_yaml = yaml.dump(resources_json)
                    return resources_json
        except Exception as err:
            return None
        return None

    def get_deployment_image(self, name=None, namespace=None, cname=None):
        """获取deploy image"""

        # body = client.AppsV1Api(client.ApiClient()).read_namespaced_deployment(name,namespace)
        try:
            k8s_api=self.k8s_api(kind='Deployment')
            body = k8s_api.read_namespaced_deployment(name=name,namespace=namespace)
            for container in body.spec.template.spec.containers:
                if name == container.name or re.search(container.name, name) or cname == container.name:
                    return container.image
        except:
            return None
        return None


    def get_namespaced_deployment_image_tags(self, namespace):
        data = {}
        for item in self.get_namespaced_kind_info(kind='Deployment', namespace=namespace).items:
            name = item.metadata.name
            image = item.spec.template.spec.containers[0].image
            replicas = item.spec.replicas
            if replicas == 0: continue
            image_tag = image.split(':')[-1]
            data[name] = image_tag
        return data


    def get_namespaced_deployment_replicas_resource(self, namespace):
        data = []
        for item in self.get_namespaced_kind_info(kind='Deployment', namespace=namespace).items:
            name = item.metadata.name
            replicas = item.spec.replicas
            if replicas == 0: continue
            resources_json = {}
            java_opts = None
            for container in item.spec.template.spec.containers:
                if name == container.name or re.search(container.name, name):
                    resources = container.resources
                    # resources_json = {'limits': resources.limits, 'requests': resources.requests}
                    resources_json = resources.to_dict()
                    if container.env:
                        for e in container.env:
                            if e and e.name == 'JAVA_OPTS':
                                java_opts = e.value
            data.append({
                'name': name,
                'replicas': replicas,
                'resources': resources_json,
                'java_opts': java_opts
            })
        return data


if __name__ == '__main__':
    import time

    kubecli = KubeClient('4', '')
    kubecli.get_deployment_status(
        'zdwy-system-h5-deployment',
        namespace='cobra')
    # kubecli.deployment_update_image('order-xxx-deployment', namespace='cobra', image="123")
    # kubecli.get_namespaced_pod_list('cobra', 'order-web-deployment')
    # ret=kubecli.get_deployment_replicas('order-web-deployment', 'cobra')
    # ret=kubecli.get_namespaced_deployment_image_tags( 'cobra')
    # for i in range(100):
    #     ret = kubecli.check_deployment_rollout_status('order-web-deployment', 'cobra')
    #     print(ret)
    #     print('sleep 5s loop {}'.format(i))
    #     time.sleep(5)
